//
//  ModelBinder.swift
//  TuJiaMerchant
//
//  Created by 何海涛 on 16/1/7.
//  Copyright © 2016年 途家. All rights reserved.
//

import Foundation

public class ModelBinder {
    /**
     Model type validation without model binding.
     
     - parameter rules: Validation rule provider.
     - parameter view: Value provider
     
     - returns: KeyValues, validation errors.
     */
    static public func validate(_ rules: IValidationRuleProvider, view: IViewDataBindingProtocol) -> (vals: [String:AnyObject], errors:[ValidationResult]) {
        return validate(rules, views: [view])
    }
    
    /**
     Model type validation without model binding.
     
     - parameter rules: Validation rule provider.
     - parameter views: All value providers
     
     - returns: KeyValues, validation errors.
     */
    static public func validate(_ rules: IValidationRuleProvider, views: [IViewDataBindingProtocol]) -> (vals: [String:AnyObject], errors:[ValidationResult]) {
        let rules = rules.getValidateRules()
        let mappings : [ViewDataMapping] = views.map({ $0.getDataMappings() }).reduce([ViewDataMapping](), { $0 + $1 })
        
        var keyValues = [String: AnyObject]()
        mappings.forEach { (mapping : ViewDataMapping) -> () in
            if let val = mapping.valueProvider() {
                keyValues[mapping.memberName] = val
            }
        }
        
        var errors = [ValidationResult]()
        for rule in rules {
            let val = keyValues[rule.memberName]
            if let error = rule.validate(val, allVals: keyValues) {
                error.mapping = mappings.first({ $0.memberName == rule.memberName })
                errors.append(error)
            }
        }
        
        return (keyValues, errors)
    }
    
    /**
     Single property validation.
     
     - parameter rules:         Validation rule provider.
     - parameter view:          Data mapping provider
     - parameter memberName:    Member(property) name.
     
     - returns: property val, validation errors
     */
    static public func validateForMember(_ rules: IValidationRuleProvider, view: IViewDataBindingProtocol, memberName forMember: String) -> (val: AnyObject?, errors: [ValidationResult]) {
        return validateForMember(rules, views: [view], memberName: forMember)
    }
    
    /**
     Single property validation.
     
     - parameter rules:         Validation rule provider.
     - parameter views:         All data mapping providers
     - parameter memberName:    Member(property) name.
     
     - returns: property val, validation errors
     */
    static public func validateForMember(_ rules: IValidationRuleProvider, views: [IViewDataBindingProtocol], memberName forMember: String) -> (val: AnyObject?, errors: [ValidationResult]) {
        let validateResult = validate(rules, views: views)
        let val = validateResult.vals[forMember]
        let errors = validateResult.errors.filter({ $0.memberName == forMember })
        
        return (val, errors)
    }
    
    /**
     Model bind
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter view:          Data mapping provider.
     
     - returns: model, validation errors
     */
    public static func bindModel<T:ISerializeCopyableProtocol>(_ model: T, rules: IValidationRuleProvider, view: IViewDataBindingProtocol) -> (model:T, errors: [ValidationResult]) {
        return bindModel(model, rules: rules, views: [view])
    }
    
    /**
     Model bind
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter views:         All data mapping providers.
     
     - returns: model, validation errors
     */
    public static func bindModel<T:ISerializeCopyableProtocol>(_ model: T, rules: IValidationRuleProvider, views: [IViewDataBindingProtocol]) -> (model:T, errors: [ValidationResult]) {
        
        //validate
        let validateResult = validate(rules, views: views)
        
        //copy values
        model.copyValuesWithJson(JSON(validateResult.vals))
        return (model, validateResult.errors)
    }
    
    /**
     Model bind with error behavior.
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter view:          Data mapping provider.
     - parameter errorBehavior: Error behavior
     - note:                    For error behavior, default value is <b>ValidationErrorBehaviors.Default</b>
     
     - returns: model.
     */
    public static func bindModelWithErrorBehavior<T: ISerializeCopyableProtocol>(_ model: T, rules: IValidationRuleProvider, view: IViewDataBindingProtocol, errorBehavior: ValidationErrorBehaviorProtocol = ValidationErrorBehaviors.Default) -> (model:T, valid: Bool) {
        return bindModelWithErrorBehavior(model, rules: rules, views: [view], errorBehavior: errorBehavior)
    }
    
    /**
     Model bind with error behavior.
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter views:         All data mapping provider.
     - parameter errorBehavior: Error behavior
     - note:                    For error behavior, default value is <b>ValidationErrorBehaviors.Default</b>
     
     - returns: model.
     */
    public static func bindModelWithErrorBehavior<T:ISerializeCopyableProtocol>(_ model: T, rules: IValidationRuleProvider, views: [IViewDataBindingProtocol], errorBehavior: ValidationErrorBehaviorProtocol = ValidationErrorBehaviors.Default) -> (model:T, valid: Bool) {
        let bindResult : (model: T, errors : [ValidationResult]) = bindModel(model, rules: rules, views: views)
        
        //behavior
        errorBehavior.handleError(errors: bindResult.errors)
        return (bindResult.model, bindResult.errors.count == 0)
    }
    
    /**
     Model bind (Model whick implement 2 protocol can ignore parameter of rule provider)
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter view:          Data mapping provider.
     
     - returns: model, validation errors
     */
    public static func bindModel<T: ISerializeCopyableProtocol & IValidationRuleProvider>(_ model: T, view: IViewDataBindingProtocol) -> (model:T, errors: [ValidationResult]) {
        return bindModel(model, rules: model, views: [view])
    }
    
    /**
     Model bind (Model whick implement 2 protocol can ignore parameter of rule provider)
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter views:         All data mapping providers.
     
     - returns: model, validation errors
     */
    public static func bindModel<T: ISerializeCopyableProtocol & IValidationRuleProvider>(_ model: T, views: [IViewDataBindingProtocol]) -> (model:T, errors: [ValidationResult]) {
        
        //validate
        let validateResult = validate(model, views: views)
        
        //copy values
        model.copyValuesWithJson(JSON(validateResult.vals))
        return (model, validateResult.errors)
    }
    
    /**
     Model bind with error behavior. (Model whick implement 2 protocol can ignore parameter of rule provider)
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter view:          Data mapping provider.
     - parameter errorBehavior: Error behavior
     - note:                    For error behavior, default value is <b>ValidationErrorBehaviors.Default</b>
     
     - returns: model.
     */
    public static func bindModelWithErrorBehavior<T: ISerializeCopyableProtocol & IValidationRuleProvider>(_ model: T, view: IViewDataBindingProtocol, errorBehavior: ValidationErrorBehaviorProtocol = ValidationErrorBehaviors.Default) -> (model:T, valid: Bool) {
        return bindModelWithErrorBehavior(model, rules: model, views: [view], errorBehavior: errorBehavior)
    }
    
    /**
     Model bind with error behavior. (Model whick implement 2 protocol can ignore parameter of rule provider)
     
     - parameter model:         Model to be bound properties.
     - parameter rules:         Validation rule provider.
     - parameter views:         All data mapping provider.
     - parameter errorBehavior: Error behavior
     - note:                    For error behavior, default value is <b>ValidationErrorBehaviors.Default</b>
     
     - returns: model.
     */
    public static func bindModelWithErrorBehavior<T: ISerializeCopyableProtocol & IValidationRuleProvider>(_ model: T, views: [IViewDataBindingProtocol], errorBehavior: ValidationErrorBehaviorProtocol = ValidationErrorBehaviors.Default) -> (model:T, valid: Bool) {
        let bindResult : (model: T, errors : [ValidationResult]) = bindModel(model, rules: model, views: views)
        
        //behavior
        errorBehavior.handleError(errors: bindResult.errors)
        return (bindResult.model, bindResult.errors.count == 0)
    }
}
